// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! This crate provides Protocol Buffers definitions for the services provided by the
//! [`storage_service`](../storage_service/index.html) crate.
//!
//! The protocol is documented in Protocol Buffers sources files in the `.proto` extension and the
//! documentation is not viewable via rustdoc. Refer to the source code to see it.
//!
//! The content provided in this documentation falls to two categories:
//!
//!   1. Those automatically generated by [`grpc-rs`](https://github.com/pingcap/grpc-rs):
//!       * In [`proto::storage`] are structs corresponding to our Protocol Buffers messages.
//!       * In [`proto::storage_grpc`] live the [GRPC](grpc.io) client struct and the service trait
//! which correspond to our Protocol Buffers services.
//!   1. Structs we wrote manually as helpers to ease the manipulation of the above category of
//! structs. By implementing the [`FromProto`](proto_conv::FromProto) and
//! [`IntoProto`](proto_conv::IntoProto) traits, these structs convert from/to the above category of
//! structs in a single method call and in that process data integrity check can be done. These live
//! right in the root module of this crate (this page).
//!
//! Ihis is provided as a separate crate so that crates that use the storage service via
//! [`storage_client`](../storage_client/index.html) don't need to depending on the entire
//! [`storage_service`](../storage_client/index.html).

#![allow(clippy::unit_arg)]

pub mod proto;

use crypto::HashValue;
use failure::prelude::*;
use proptest_derive::Arbitrary;
use proto_conv::{FromProto, IntoProto};
use types::{
    account_address::AccountAddress,
    account_state_blob::AccountStateBlob,
    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
    proof::definition::SparseMerkleProof,
    transaction::{TransactionListWithProof, TransactionToCommit, Version},
};

/// Helper to construct and parse [`proto::storage::GetAccountStateWithProofByStateRootRequest`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(PartialEq, Eq, Clone, FromProto, IntoProto)]
#[ProtoType(crate::proto::storage::GetAccountStateWithProofByStateRootRequest)]
pub struct GetAccountStateWithProofByStateRootRequest {
    /// The access path to query with.
    pub address: AccountAddress,

    /// the state root hash the query is based on.
    pub state_root_hash: HashValue,
}

impl GetAccountStateWithProofByStateRootRequest {
    /// Constructor.
    pub fn new(address: AccountAddress, state_root_hash: HashValue) -> Self {
        Self {
            address,
            state_root_hash,
        }
    }
}

/// Helper to construct and parse [`proto::storage::GetAccountStateWithProofByStateRootResponse`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(PartialEq, Eq, Clone)]
pub struct GetAccountStateWithProofByStateRootResponse {
    /// The account state blob requested.
    pub account_state_blob: Option<AccountStateBlob>,

    /// The state root hash the query is based on.
    pub sparse_merkle_proof: SparseMerkleProof,
}

impl GetAccountStateWithProofByStateRootResponse {
    /// Constructor.
    pub fn new(
        account_state_blob: Option<AccountStateBlob>,
        sparse_merkle_proof: SparseMerkleProof,
    ) -> Self {
        Self {
            account_state_blob,
            sparse_merkle_proof,
        }
    }
}

impl FromProto for GetAccountStateWithProofByStateRootResponse {
    type ProtoType = crate::proto::storage::GetAccountStateWithProofByStateRootResponse;

    fn from_proto(mut object: Self::ProtoType) -> Result<Self> {
        let account_state_blob = if object.has_account_state_blob() {
            Some(AccountStateBlob::from_proto(
                object.take_account_state_blob(),
            )?)
        } else {
            None
        };
        Ok(Self {
            account_state_blob,
            sparse_merkle_proof: SparseMerkleProof::from_proto(object.take_sparse_merkle_proof())?,
        })
    }
}

impl IntoProto for GetAccountStateWithProofByStateRootResponse {
    type ProtoType = crate::proto::storage::GetAccountStateWithProofByStateRootResponse;

    fn into_proto(self) -> Self::ProtoType {
        let mut object = Self::ProtoType::new();

        if let Some(account_state_blob) = self.account_state_blob {
            object.set_account_state_blob(account_state_blob.into_proto());
        }
        object.set_sparse_merkle_proof(self.sparse_merkle_proof.into_proto());
        object
    }
}

impl Into<(Option<AccountStateBlob>, SparseMerkleProof)>
    for GetAccountStateWithProofByStateRootResponse
{
    fn into(self) -> (Option<AccountStateBlob>, SparseMerkleProof) {
        (self.account_state_blob, self.sparse_merkle_proof)
    }
}

/// Helper to construct and parse [`proto::storage::SaveTransactionsRequest`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(Arbitrary, Clone, Debug, Eq, PartialEq)]
pub struct SaveTransactionsRequest {
    pub txns_to_commit: Vec<TransactionToCommit>,
    pub first_version: Version,
    pub ledger_info_with_signatures: Option<LedgerInfoWithSignatures>,
}

impl SaveTransactionsRequest {
    /// Constructor.
    pub fn new(
        txns_to_commit: Vec<TransactionToCommit>,
        first_version: Version,
        ledger_info_with_signatures: Option<LedgerInfoWithSignatures>,
    ) -> Self {
        SaveTransactionsRequest {
            txns_to_commit,
            first_version,
            ledger_info_with_signatures,
        }
    }
}

impl FromProto for SaveTransactionsRequest {
    type ProtoType = crate::proto::storage::SaveTransactionsRequest;

    fn from_proto(mut object: Self::ProtoType) -> Result<Self> {
        let txns_to_commit = object
            .take_txns_to_commit()
            .into_iter()
            .map(TransactionToCommit::from_proto)
            .collect::<Result<Vec<_>>>()?;
        let first_version = object.get_first_version();
        let ledger_info_with_signatures = object
            .ledger_info_with_signatures
            .take()
            .map(LedgerInfoWithSignatures::from_proto)
            .transpose()?;

        Ok(Self {
            txns_to_commit,
            first_version,
            ledger_info_with_signatures,
        })
    }
}

impl IntoProto for SaveTransactionsRequest {
    type ProtoType = crate::proto::storage::SaveTransactionsRequest;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_txns_to_commit(::protobuf::RepeatedField::from_vec(
            self.txns_to_commit
                .into_iter()
                .map(TransactionToCommit::into_proto)
                .collect::<Vec<_>>(),
        ));
        proto.set_first_version(self.first_version);
        if let Some(x) = self.ledger_info_with_signatures {
            proto.set_ledger_info_with_signatures(x.into_proto())
        }

        proto
    }
}

/// Helper to construct and parse [`proto::storage::GetTransactionsRequest`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(Arbitrary, Clone, Debug, Eq, PartialEq)]
pub struct GetTransactionsRequest {
    pub start_version: Version,
    pub batch_size: u64,
    pub ledger_version: Version,
    pub fetch_events: bool,
}

impl GetTransactionsRequest {
    /// Constructor.
    pub fn new(
        start_version: Version,
        batch_size: u64,
        ledger_version: Version,
        fetch_events: bool,
    ) -> Self {
        GetTransactionsRequest {
            start_version,
            batch_size,
            ledger_version,
            fetch_events,
        }
    }
}

impl FromProto for GetTransactionsRequest {
    type ProtoType = crate::proto::storage::GetTransactionsRequest;

    fn from_proto(object: Self::ProtoType) -> Result<Self> {
        Ok(GetTransactionsRequest {
            start_version: object.get_start_version(),
            batch_size: object.get_batch_size(),
            ledger_version: object.get_ledger_version(),
            fetch_events: object.get_fetch_events(),
        })
    }
}

impl IntoProto for GetTransactionsRequest {
    type ProtoType = crate::proto::storage::GetTransactionsRequest;

    fn into_proto(self) -> Self::ProtoType {
        let mut out = Self::ProtoType::new();
        out.set_start_version(self.start_version);
        out.set_batch_size(self.batch_size);
        out.set_ledger_version(self.ledger_version);
        out.set_fetch_events(self.fetch_events);
        out
    }
}

/// Helper to construct and parse [`proto::storage::GetTransactionsResponse`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(Arbitrary, Clone, Debug, Eq, PartialEq, FromProto, IntoProto)]
#[ProtoType(crate::proto::storage::GetTransactionsResponse)]
pub struct GetTransactionsResponse {
    pub txn_list_with_proof: TransactionListWithProof,
}

impl GetTransactionsResponse {
    /// Constructor.
    pub fn new(txn_list_with_proof: TransactionListWithProof) -> Self {
        GetTransactionsResponse {
            txn_list_with_proof,
        }
    }
}

/// Helper to construct and parse [`proto::storage::ExecutorStartupInfo`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(Arbitrary, Clone, Debug, Eq, PartialEq)]
pub struct ExecutorStartupInfo {
    pub ledger_info: LedgerInfo,
    pub latest_version: Version,
    pub account_state_root_hash: HashValue,
    pub ledger_frozen_subtree_hashes: Vec<HashValue>,
}

impl FromProto for ExecutorStartupInfo {
    type ProtoType = crate::proto::storage::ExecutorStartupInfo;

    fn from_proto(mut object: Self::ProtoType) -> Result<Self> {
        let ledger_info = LedgerInfo::from_proto(object.take_ledger_info())?;
        let latest_version = object.get_latest_version();
        let account_state_root_hash = HashValue::from_proto(object.take_account_state_root_hash())?;
        let ledger_frozen_subtree_hashes = object
            .take_ledger_frozen_subtree_hashes()
            .into_iter()
            .map(HashValue::from_proto)
            .collect::<Result<Vec<_>>>()?;

        Ok(Self {
            ledger_info,
            latest_version,
            account_state_root_hash,
            ledger_frozen_subtree_hashes,
        })
    }
}

impl IntoProto for ExecutorStartupInfo {
    type ProtoType = crate::proto::storage::ExecutorStartupInfo;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_ledger_info(self.ledger_info.into_proto());
        proto.set_latest_version(self.latest_version);
        proto.set_account_state_root_hash(self.account_state_root_hash.into_proto());
        proto.set_ledger_frozen_subtree_hashes(protobuf::RepeatedField::from_vec(
            self.ledger_frozen_subtree_hashes
                .into_iter()
                .map(HashValue::into_proto)
                .collect::<Vec<_>>(),
        ));
        proto
    }
}

/// Helper to construct and parse [`proto::storage::GetExecutorStartupInfoResponse`]
///
/// It does so by implementing [`IntoProto`](#impl-IntoProto) and [`FromProto`](#impl-FromProto),
/// providing [`into_proto`](IntoProto::into_proto) and [`from_proto`](FromProto::from_proto).
#[derive(Arbitrary, Clone, Debug, Eq, PartialEq)]
pub struct GetExecutorStartupInfoResponse {
    pub info: Option<ExecutorStartupInfo>,
}

impl FromProto for GetExecutorStartupInfoResponse {
    type ProtoType = crate::proto::storage::GetExecutorStartupInfoResponse;

    fn from_proto(mut object: Self::ProtoType) -> Result<Self> {
        let info = if object.has_info() {
            Some(ExecutorStartupInfo::from_proto(object.take_info())?)
        } else {
            None
        };

        Ok(Self { info })
    }
}

impl IntoProto for GetExecutorStartupInfoResponse {
    type ProtoType = crate::proto::storage::GetExecutorStartupInfoResponse;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        if let Some(info) = self.info {
            proto.set_info(info.into_proto())
        }
        proto
    }
}

pub mod prelude {
    pub use super::*;
}

#[cfg(test)]
mod tests;
